package cn.wolfcode.wolf2w.business.service.impl;

import cn.wolfcode.wolf2w.business.api.RemoteDestinationService;
import cn.wolfcode.wolf2w.business.api.RemoteNoteService;
import cn.wolfcode.wolf2w.business.api.RemoteStrategyService;

import cn.wolfcode.wolf2w.business.api.RemoteUserInfoEsService;
import cn.wolfcode.wolf2w.business.api.domain.*;

import cn.wolfcode.wolf2w.business.query.SearchQuery;
import cn.wolfcode.wolf2w.business.repostory.DestinationEsRepository;
import cn.wolfcode.wolf2w.business.repostory.NoteEsRepository;
import cn.wolfcode.wolf2w.business.repostory.StrategyEsRepository;
import cn.wolfcode.wolf2w.business.repostory.UserInfoEsRepository;
import cn.wolfcode.wolf2w.business.service.ISearchService;
import cn.wolfcode.wolf2w.common.core.constant.SecurityConstants;

import cn.wolfcode.wolf2w.member.api.RemoteUserInfoService;
import cn.wolfcode.wolf2w.member.api.domain.UserInfo;
import org.apache.commons.beanutils.BeanUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class SearchServiceImpl implements ISearchService {

    @Autowired
    private StrategyEsRepository repository;
    @Autowired
    private UserInfoEsRepository userInfoEsRepository;
    @Autowired
    private NoteEsRepository noteEsRepository;
    @Autowired
    private DestinationEsRepository destinationEsRepository;
    @Autowired
    //用于执行高亮复杂查询
    private ElasticsearchRestTemplate template;
    @Autowired
    private RemoteStrategyService     remoteStrategyService;
   @Autowired
   private RemoteUserInfoService remoteUserInfoService;
   @Autowired
   private RemoteNoteService remoteNoteService;
   @Autowired
   private RemoteDestinationService remoteDestinationService;


    //保存攻略
    @Override
    public void saveStrategy(StrategyEs strategyEs) {
        repository.save(strategyEs);  //直接存储
    }
    @Override
    public void saveUserInfo(UserEs userInfoEs) {
        userInfoEsRepository.save(userInfoEs);
    }

    @Override
    public void saveNote(NoteEs noteEs) {
        noteEsRepository.save(noteEs);
    }

    @Override
    public void saveDestination(DestinationEs destinationEs) {
        destinationEsRepository.save(destinationEs);
    }

    @Override
    public <T> PageImpl<T> searchWithHightLight(Class<T> clazz, Class<?> esClazz, SearchQuery qo, String... fields) throws InvocationTargetException, IllegalAccessException {
        //设置查询条件
        NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();
        //qo.getKeyword() 的作用：提供搜索关键词
        //第一个参数需要传入 “用户要搜索的关键词”（比如用户在搜索框输入的 “北京旅游攻略”）
        //第二个参数 fields 是要在哪些字段中搜索该关键词
        builder.withQuery(QueryBuilders.multiMatchQuery(qo.getKeyword(), fields));

        //设置分页条件
        //非关系行数据库（mysql）是从第一页开始，es库是从第0页开始，所以需要-1
        Pageable pageable = PageRequest.of(qo.getCurrentPage()-1, qo.getPageSize());
        builder.withPageable(pageable);

        //设置高亮条件
        List<HighlightBuilder.Field> fs=new ArrayList<>();
        for (String name : fields) {
            HighlightBuilder.Field field=new HighlightBuilder.Field(name);//初始化
            field.preTags("<span style='color:red'>").postTags("</span>");//设置高亮样式的前缀和后缀
            fs.add(field);
        }
        //设置高亮
        //高亮的时候不是要名字，要的是包装好的。
        builder.withHighlightFields(fs);

/**     先通过 ES 快速搜索出匹配条件的文档（利用 ES 强大的全文检索能力）；
        从 ES 文档中提取 ID（与 MySQL 主键关联）；
        根据 ID 调用远程服务查询 MySQL 中的完整业务数据（因为 ES 通常只存储用于检索的关键字段，完整数据存于 MySQL）；
        最终将 MySQL 中的数据收集到列表，供后续业务使用（如返回给前端）。
 * ****/
        //执行查询
        //将 Elasticsearch（ES）中搜索到的结果（文档 ID）与 MySQL 中的完整业务数据进行关联，最终收集并返回 MySQL 中的实体对象列表。
        //用于存储最终从 MySQL 中查询到的业务对象
        List<T> list= new ArrayList<>();
        //查询的是es库中的数据
        //builder.build() 是之前构建的 ES 查询条件
        // esClazz 是 ES 中存储的文档对应的实体类
        //执行后返回 SearchHits<?> 对象，包含 ES 搜索的所有结果：包括命中的文档（SearchHit）、总命中数等信息。
        SearchHits<?> hits= template.search(builder.build(), esClazz);//查询总的结果
        //获取 ES 搜索结果的总命中数（即匹配查询条件的文档总数），通常用于后续分页信息的构建（如总条数、总页数等）
        long totalCount = hits.getTotalHits();
        //循环处理每个 ES 命中的文档
        for (SearchHit<?> searchHit : hits.getSearchHits()) {  //查询出来的是一个数组
            // 获取 ES 文档的 ID（通常与 MySQL 中对应记录的主键一致）
            Long id = Long.valueOf(searchHit.getId());
            // 声明泛型变量 t，用于存储从 MySQL 中查询到的对象
            T t=null;//用于存储mysql 中的对象
            if(clazz== Strategy.class){
                // 调用远程服务（如微服务中的 Strategy 服务）根据 ID 查询 MySQL 中的数据
                //因为es中数据不全，所以需要从mysql中查询
               t=(T) remoteStrategyService.getOne(id, SecurityConstants.INNER).getData();
                // 将查询到的对象添加到结果列表中

            }else  if(clazz== UserInfo.class){
                t=(T) remoteUserInfoService.getOne(id, SecurityConstants.INNER).getData();
            }else if(clazz== Note.class){
                t=(T) remoteNoteService.getOne(id, SecurityConstants.INNER).getData();
            }else if(clazz== Destination.class){
                t=(T) remoteDestinationService.getOne(id, SecurityConstants.INNER).getData();
            }
            list.add(t);
            /**
             从搜索结果对象searchHit中获取高亮字段的映射集合。
                键（String）：需要高亮显示的字段名（例如 "title"、"content" 等）。
                值（List<String>）：该字段对应的高亮内容片段列表（一个字段可能有多个匹配的高亮片段）。**/
            Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
            //遍历所有的key,循环遍历映射中所有的字段名（即需要高亮的字段）。
            for (String key : highlightFields.keySet()) {
                //根据当前字段名key，获取该字段对应的所有高亮内容片段（例如搜索关键词被<em>包裹的文本片段）。
                List<String> arr = highlightFields.get(key);
                //使用StringBuffer拼接列表中的所有高亮片段，片段之间                                                                                                                                                                                                                                                                                                                                                    用空格分隔。
                //最终得到该字段完整的高亮文本（例如"这是<em>关键词</em>的第一个片段 这是<em>关键词</em>的第二个片段"）。
                StringBuffer stringBuffer=new StringBuffer();
                for (String string : arr) {
                    stringBuffer.append(string).append(" ");
                }
                String value = stringBuffer.toString();
                //设置t对象属性未value值,把原来属性替换成高亮查询
                //使用BeanUtils工具类的反射机制，将数据对象t中对应字段（字段名是key）的原值替换为拼接后的高亮文本value。
                BeanUtils.setProperty(t, key, value);
            }
        }

        /*参数1：当前页数据
        参数2：分页条件
        参数3：总条数

        一个分页对象中封装了当前页数据、每页条数，总条数，总页数，当前页数据，上一页，下一页
        * */
        return new PageImpl<T>(list,pageable,totalCount);
    }



}
